package com.pickup.main;


import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;

import javax.smartcardio.Card;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CardTerminal;
import javax.smartcardio.CardTerminals;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
import javax.smartcardio.TerminalFactory;

/**
 * javax.smartcardio，操作PCSC规范的读卡器
 * 共有16个扇区，每个扇区4个区块
 * IC卡的0扇区0区块放着这张卡的序列号~一般是出厂时就固化不可更改的~
 * 而且！读取序列号不需要验证密码
 * 区块地址从00向上递增。
 * 其中，每个扇区的第三区块是密码和控制字存储的区块，不能作为数据存储使用。
 * 还有一个特例，就是0扇区的0区块，存储的是卡片的序列号，不可更改。
 * 每个扇区只需认证一次密钥即可对三个数据块随意读写。
 * 出厂默认的控制字FF078069表示KEYA 或者KEYB都可以随意读写。
 * @author Reid（QQ1184010001）
 */
public class LetturaSmartCard {

    //------------------------------------lettura---------------------------
    //十六进制数
    private static final char[] HEX_CHAR = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
    //先用一个数组把密钥存起来
    private static final byte[] pwd = {(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff};
    //认证数据字节，包含了需要认证的区块号、密钥类型和密钥存储的地址(密钥号)
    private static final byte[] check = {(byte)0x01,(byte)0x00,(byte)0x08,(byte)0x60,(byte)0x00};

    /**
     * 连接读卡器
     */
    public static String smartCard(){
        TerminalFactory factory = TerminalFactory.getDefault();

        //清除所有终端列表
        try {
            Class<?> pcscterminal = Class.forName("sun.security.smartcardio.PCSCTerminals");
            Field contextId = pcscterminal.getDeclaredField("contextId");
            contextId.setAccessible(true);

            if(contextId.getLong(pcscterminal) != 0L)
            {
                // 首先获取一个新的上下文值
                Class<?> pcsc = Class.forName("sun.security.smartcardio.PCSC");
                Method SCardEstablishContext = pcsc.getDeclaredMethod("SCardEstablishContext", new Class[] {Integer.TYPE });
                SCardEstablishContext.setAccessible(true);

                Field SCARD_SCOPE_USER = pcsc.getDeclaredField("SCARD_SCOPE_USER");
                SCARD_SCOPE_USER.setAccessible(true);

                long newId = ((Long)SCardEstablishContext.invoke(pcsc,
                        new Object[] { SCARD_SCOPE_USER.getInt(pcsc) }
                ));
                contextId.setLong(pcscterminal, newId);


                // 然后清除缓存中的终端
                CardTerminals terminals = factory.terminals();
                Field fieldTerminals = pcscterminal.getDeclaredField("terminals");
                fieldTerminals.setAccessible(true);
                Class<?> classMap = Class.forName("java.util.Map");
                Method clearMap = classMap.getDeclaredMethod("clear");

                clearMap.invoke(fieldTerminals.get(terminals));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        //重新获取终端列表
        List<CardTerminal> terminals;
        try {
            terminals = factory.terminals().list();//get读卡器列表
            System.out.println("阅读器连接列表: " + terminals);
            CardTerminal a = terminals.get(0);//使用第0个读卡器[暂且不考虑同时插N个读卡器的情况了]
            System.out.println("第一个终端: " + a);
            a.waitForCardPresent(0L);//等待放置卡片
            Card card = a.connect("T=1");//连接卡片，协议T=1 块读写(T=0貌似不支持，一用就报错)
            CardChannel channel = card.getBasicChannel();//打开通道
            CommandAPDU getUID = new CommandAPDU(0xFF, 0xCA, 0x00, 0x00,0x04);//中文API第12页
            ResponseAPDU r = channel.transmit(getUID);//发送getUID指令
            //CommandAPDU loadPWD = new CommandAPDU(0xFF, 0x82, 0x00, 0x00, pwd,0,6);//然后构造一个加载密钥APDU指令~
            //ResponseAPDU r = channel.transmit(loadPWD);//发送loadPWD指令
            //CommandAPDU getData = new CommandAPDU(0xFF, 0xB0, 0x00, 0x08,0x10);//构造读区块APDU指令,读第八个区块(2扇区0区块)值
            //ResponseAPDU r = channel.transmit(getData);
            System.out.println(r.getBytes());
            System.out.println("bytesToHexString结果: " + bytesToHexString(r.getBytes()));
            System.out.println("bytesToHex结果: " + bytesToHex(r.getBytes()));
            String UID = bytesToHexString(r.getData());
            return UID;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Utils
     * 因为数据返回是一个byte[]数组，文档和API使用的是16进制数，
     * 所以需要一个将byte[]转为十六进制数的小方法
     * @param bytes byte[]数组
     * @return 字符串
     */
    public static String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        int a = 0;
        for (byte b : bytes) { // 使用除与取余进行转换
            if (b < 0) {
                a = 256 + b;
            } else {
                a = b;
            }
            sb.append(HEX_CHAR[a / 16]);
            sb.append(HEX_CHAR[a % 16]);
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 读取风扇区
     * @param bytes byte[]数组
     * @return 字符串
     */
    public static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (int i = 0; i < bytes.length; i++) {
            sb.append(String.format("%02x", bytes[i]));
        }

        return sb.toString();
    }
}
